﻿using System.IO.IsolatedStorage;
using System.IO;
using System;
using System.Linq;
using System.Runtime.Serialization;
using System.Xml;
using System.Xml.Linq;

namespace MichMan.Utilities.Requests
{
    [DataContract]
    public class IsoCacheInfo
    {
        [DataMember]
        public DateTime? Expires { get; set; }
        [DataMember]
        public string AssemblyQualifiedTypeName { get; set; }
    }

    public class IsolatedStorageFileCache : CacheBase, ICache
    {
        static bool _done = false;
        public IsolatedStorageFileCache()
        {
            // Do this once.  That way, the first time this cache is accessed the timer can start if necessary.
            if (!_done)
            {
                UpdateExpiryTimer();
                _done = true;
            }
        }

        private FileManager FileManager
        {
            get
            {
                return FileManager.Instance;
            }
        }

        /// <summary>
        /// When the next item will expire.  Return TimeSpan.MaxValue to indicate that no items will expire.
        /// </summary>
        /// <returns></returns>
        protected override TimeSpan GetNextExpiry()
        {
            DateTime now = DateTime.UtcNow;
            var cachedItems = FileManager.GetFilesWithMetadata<IsoCacheInfo>();
            var toExpire = (from item in cachedItems where item.Metadata.Get<IsoCacheInfo>().Expires.HasValue && item.Metadata.Get<IsoCacheInfo>().Expires < now select item.Metadata.Get<IsoCacheInfo>().Expires.Value - now).ToList();
            if (!toExpire.Any())
            {
                return TimeSpan.MaxValue;
            }
            return toExpire.OrderBy(exp => exp).First();
        }

        /// <summary>
        /// Called periodically to remove entries that have expired.
        /// Must be implemented if you are using the Expiry timer.
        /// </summary>
        protected override void RemoveExpiredEntries()
        {
            DateTime now = DateTime.UtcNow;
            var cachedItems = FileManager.GetFilesWithMetadata<IsoCacheInfo>();
            var expired = (from item in cachedItems where item.Metadata.Get<IsoCacheInfo>().Expires.HasValue && item.Metadata.Get<IsoCacheInfo>().Expires < now select item.Uri).ToList();
            foreach (var key in expired)
            {
                Delete(key);
            }
        }

        public object Get(string key)
        {
            ManagedFileInfo mfi = FileManager.FindByUri(key);
            if (mfi == null)
            {
                return null;
            }

            IsoCacheInfo info = mfi.Metadata.Get<IsoCacheInfo>();
            if (info != null && info.Expires.HasValue)
            {
                if (info.Expires < DateTime.UtcNow)
                {
                    Delete(key);
                    return null;
                }
            }

            if (mfi.IsComplete)
            {
                try
                {
                    // Return the object.
                    IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication();
                    if (!isoFile.FileExists(mfi.Path))
                    {
                        return null;
                    }

                    using (Stream stream = isoFile.OpenFile(mfi.Path, FileMode.Open, FileAccess.Read, FileShare.Read))
                    {
                        using (StreamReader reader = new StreamReader(stream))
                        {
                            string str = reader.ReadToEnd();
                            if (info == null)
                            {
                                return reader.ReadToEnd();
                            }
                            Type t = Type.GetType(info.AssemblyQualifiedTypeName);
                            
                            if (t == typeof(string))
                            {
                                return reader.ReadToEnd();
                            }
                            else if (DataContractUtility.IsDataContractType(t))
                            {
                                using (XmlReader xmlReader = XmlReader.Create(reader))
                                {
                                    return DataContractUtility.ReadDataContract(xmlReader, t);
                                }
                            }
                            else if (ZPathSerializer.IsZPathSerializable(t))
                            {
                                return ZPathSerializer.ReadZPathObject(t, XElement.Parse(reader.ReadToEnd()));
                            }
                        }
                    }
                }
                catch
                {
                    return null;
                }
            }

            return null;
        }

        public void Delete(string key)
        {
            ManagedFileInfo mfi = FileManager.FindByUri(key);
            if (mfi == null)
            {
                return;
            }

            try
            {
                IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication();
                if (isoFile.FileExists(mfi.Path))
                {
                    isoFile.DeleteFile(mfi.Path);
                }
            }
            finally
            {
                FileManager.Remove(mfi);
            }
        }
        
        public void Clear()
        {
            IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication();
            isoFile.Remove();
        }

        public bool Insert(string key, object obj, TimeSpan? lifetime)
        {
            ManagedFileInfo mfi = FileManager.Instance.NewFileInfo(key, null);
            if (lifetime.HasValue)
            {
                mfi.Metadata.Put<IsoCacheInfo>(new IsoCacheInfo() { Expires = DateTime.UtcNow + lifetime, AssemblyQualifiedTypeName = obj.GetType().AssemblyQualifiedName });
            }
            else
            {
                mfi.Metadata.Put<IsoCacheInfo>(new IsoCacheInfo() { AssemblyQualifiedTypeName = obj.GetType().AssemblyQualifiedName });
            }

            if (obj is String)
            {
                try
                {
                    IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication();
                    using (Stream stream = isoFile.CreateFile(mfi.Path))
                    {
                        using (TextWriter writer = new StreamWriter(stream))
                        {
                            writer.Write(obj as string);
                            writer.Flush();
                            stream.Flush();
                            stream.Seek(0, SeekOrigin.Begin);
                            FileManager.Instance.Add(mfi);
                            return true;
                        }
                    }
                }
                catch
                {
                    return false;
                }
            }
            return false;
        }
    }
}